#include "gtkrevealer.h"
#include "gtksearchentryprivate.h"
#include "gtksnapshot.h"
+#include "gtkeventcontrollerkey.h"
/**
* SECTION:gtksearchbar
* built-in. The search bar would appear when a search is started through
* typing on the keyboard, or the application’s search mode is toggled on.
*
- * For keyboard presses to start a search, events will need to be
- * forwarded from the top-level window that contains the search bar.
- * See gtk_search_bar_handle_event() for example code. Common shortcuts
- * such as Ctrl+F should be handled as an application action, or through
- * the menu items.
+ * For keyboard presses to start a search, the search bar must be told
+ * of a widget to capture key events from through
+ * gtk_search_bar_set_key_capture_widget(). This widget will typically
+ * be the top-level window, or a parent container of the search bar. Common
+ * shortcuts such as Ctrl+F should be handled as an application action, or
+ * through the menu items.
*
* You will also need to tell the search bar about which entry you
* are using as your search entry using gtk_search_bar_connect_entry().
GtkWidget *entry;
gboolean reveal_child;
+
+ GtkWidget *capture_widget;
+ GtkEventController *capture_widget_controller;
} GtkSearchBarPrivate;
G_DEFINE_TYPE_WITH_PRIVATE (GtkSearchBar, gtk_search_bar, GTK_TYPE_BIN)
gtk_revealer_set_reveal_child (GTK_REVEALER (priv->revealer), FALSE);
}
-static gboolean
-entry_key_pressed_event_cb (GtkWidget *widget,
- GdkEvent *event,
- GtkSearchBar *bar)
-{
- guint keyval;
-
- gdk_event_get_keyval (event, &keyval);
-
- if (keyval == GDK_KEY_Escape)
- {
- stop_search_cb (widget, bar);
- return GDK_EVENT_STOP;
- }
- else
- return GDK_EVENT_PROPAGATE;
-}
-
static void
preedit_changed_cb (GtkEntry *entry,
GtkWidget *popup,
guint preedit_change_id;
gboolean res;
char *old_text, *new_text;
- guint keyval;
+ guint keyval, state;
gdk_event_get_keyval (event, &keyval);
+ gdk_event_get_state (event, &state);
-
- if (gtk_search_entry_is_keynav_event (event) ||
+ if (gtk_search_entry_is_keynav (keyval, state) ||
keyval == GDK_KEY_space ||
keyval == GDK_KEY_Menu)
return GDK_EVENT_PROPAGATE;
}
gtk_search_bar_set_entry (bar, NULL);
+ gtk_search_bar_set_key_capture_widget (bar, NULL);
G_OBJECT_CLASS (gtk_search_bar_parent_class)->dispose (object);
}
if (priv->entry != NULL)
{
if (GTK_IS_SEARCH_ENTRY (priv->entry))
- g_signal_handlers_disconnect_by_func (priv->entry, stop_search_cb, bar);
- else
- g_signal_handlers_disconnect_by_func (priv->entry, entry_key_pressed_event_cb, bar);
+ {
+ gtk_search_entry_set_key_capture_widget (GTK_SEARCH_ENTRY (priv->entry), NULL);
+ g_signal_handlers_disconnect_by_func (priv->entry, stop_search_cb, bar);
+ }
g_object_remove_weak_pointer (G_OBJECT (priv->entry), (gpointer *) &priv->entry);
}
{
g_object_add_weak_pointer (G_OBJECT (priv->entry), (gpointer *) &priv->entry);
if (GTK_IS_SEARCH_ENTRY (priv->entry))
- g_signal_connect (priv->entry, "stop-search",
- G_CALLBACK (stop_search_cb), bar);
- else
- g_signal_connect (priv->entry, "key-press-event",
- G_CALLBACK (entry_key_pressed_event_cb), bar);
+ {
+ g_signal_connect (priv->entry, "stop-search",
+ G_CALLBACK (stop_search_cb), bar);
+ gtk_search_entry_set_key_capture_widget (GTK_SEARCH_ENTRY (priv->entry),
+ GTK_WIDGET (bar));
+ }
+
}
}
g_object_notify (G_OBJECT (bar), "show-close-button");
}
}
+
+static void
+changed_cb (gboolean *changed)
+{
+ *changed = TRUE;
+}
+
+static gboolean
+capture_widget_key_handled (GtkEventControllerKey *controller,
+ guint keyval,
+ guint keycode,
+ GdkModifierType state,
+ GtkSearchBar *bar)
+{
+ GtkSearchBarPrivate *priv = gtk_search_bar_get_instance_private (bar);
+ gboolean handled;
+
+ if (priv->reveal_child)
+ return GDK_EVENT_PROPAGATE;
+
+ if (priv->entry == NULL)
+ {
+ g_warning ("The search bar does not have an entry connected to it. Call gtk_search_bar_connect_entry() to connect one.");
+ return GDK_EVENT_PROPAGATE;
+ }
+
+ if (GTK_IS_SEARCH_ENTRY (priv->entry))
+ {
+ /* The search entry was told to listen to events from the search bar, so
+ * just forward the event to self, so the search entry has an opportunity
+ * to intercept those.
+ */
+ handled = gtk_event_controller_key_forward (controller, GTK_WIDGET (bar));
+ }
+ else
+ {
+ gboolean preedit_changed, buffer_changed;
+ guint preedit_change_id, buffer_change_id;
+ gboolean res;
+
+ if (gtk_search_entry_is_keynav (keyval, state) ||
+ keyval == GDK_KEY_space ||
+ keyval == GDK_KEY_Menu)
+ return GDK_EVENT_PROPAGATE;
+
+ if (keyval == GDK_KEY_Escape)
+ {
+ if (gtk_revealer_get_reveal_child (GTK_REVEALER (priv->revealer)))
+ {
+ stop_search_cb (priv->entry, bar);
+ return GDK_EVENT_STOP;
+ }
+
+ return GDK_EVENT_PROPAGATE;
+ }
+
+ handled = GDK_EVENT_PROPAGATE;
+ preedit_changed = buffer_changed = FALSE;
+ preedit_change_id = g_signal_connect_swapped (priv->entry, "preedit-changed",
+ G_CALLBACK (changed_cb), &preedit_changed);
+ buffer_change_id = g_signal_connect_swapped (priv->entry, "changed",
+ G_CALLBACK (changed_cb), &buffer_changed);
+
+ res = gtk_event_controller_key_forward (controller, priv->entry);
+
+ g_signal_handler_disconnect (priv->entry, preedit_change_id);
+ g_signal_handler_disconnect (priv->entry, buffer_change_id);
+
+ if ((res && buffer_changed) || preedit_changed)
+ handled = GDK_EVENT_STOP;
+ }
+
+ if (handled == GDK_EVENT_STOP)
+ gtk_revealer_set_reveal_child (GTK_REVEALER (priv->revealer), TRUE);
+
+ return handled;
+}
+
+/**
+ * gtk_search_bar_set_key_capture_widget:
+ * @bar: a #GtkSearchBar
+ * @widget: (nullable) (transfer none): a #GtkWidget
+ *
+ * Sets @widget as the widget that @bar will capture key events from.
+ *
+ * If key events are handled by the search bar, the bar will
+ * be shown, and the entry populated with the entered text.
+ *
+ * Since: 3.94
+ **/
+void
+gtk_search_bar_set_key_capture_widget (GtkSearchBar *bar,
+ GtkWidget *widget)
+{
+ GtkSearchBarPrivate *priv = gtk_search_bar_get_instance_private (bar);
+
+ g_return_if_fail (GTK_IS_SEARCH_BAR (bar));
+ g_return_if_fail (!widget || GTK_IS_WIDGET (widget));
+
+ if (priv->capture_widget == widget)
+ return;
+
+ if (priv->capture_widget)
+ {
+ g_clear_object (&priv->capture_widget_controller);
+ g_object_remove_weak_pointer (G_OBJECT (priv->capture_widget),
+ (gpointer *) &priv->capture_widget);
+ }
+
+ priv->capture_widget = widget;
+
+ if (widget)
+ {
+ g_object_add_weak_pointer (G_OBJECT (priv->capture_widget),
+ (gpointer *) &priv->capture_widget);
+
+ priv->capture_widget_controller = gtk_event_controller_key_new (widget);
+ gtk_event_controller_set_propagation_phase (priv->capture_widget_controller,
+ GTK_PHASE_CAPTURE);
+ g_signal_connect (priv->capture_widget_controller, "key-pressed",
+ G_CALLBACK (capture_widget_key_handled), bar);
+ g_signal_connect (priv->capture_widget_controller, "key-released",
+ G_CALLBACK (capture_widget_key_handled), bar);
+ }
+}
+
+/**
+ * gtk_search_bar_get_key_capture_widget:
+ * @bar: a #GtkSearchBar
+ *
+ * Gets the widget that @bar is capturing key events from.
+ *
+ * Returns: The key capture widget.
+ *
+ * Since: 3.94
+ **/
+GtkWidget *
+gtk_search_bar_get_key_capture_widget (GtkSearchBar *bar)
+{
+ GtkSearchBarPrivate *priv = gtk_search_bar_get_instance_private (bar);
+
+ g_return_val_if_fail (GTK_IS_SEARCH_BAR (bar), NULL);
+
+ return priv->capture_widget;
+}
#include "gtkintl.h"
#include "gtkmarshalers.h"
#include "gtkstylecontext.h"
+#include "gtkeventcontrollerkey.h"
/**
* SECTION:gtksearchentry
*
* Often, GtkSearchEntry will be fed events by means of being
* placed inside a #GtkSearchBar. If that is not the case,
- * you can use gtk_search_entry_handle_event() to pass events.
+ * you can use gtk_search_entry_set_key_capture_widget() to let it
+ * capture key input from another widget.
*/
enum {
static guint signals[LAST_SIGNAL] = { 0 };
typedef struct {
+ GtkWidget *capture_widget;
+ GtkEventController *capture_widget_controller;
+
guint delayed_changed_id;
gboolean content_changed;
gboolean search_stopped;
if (priv->delayed_changed_id > 0)
g_source_remove (priv->delayed_changed_id);
+ gtk_search_entry_set_key_capture_widget (GTK_SEARCH_ENTRY (object), NULL);
+
G_OBJECT_CLASS (gtk_search_entry_parent_class)->finalize (object);
}
}
gboolean
-gtk_search_entry_is_keynav_event (GdkEvent *event)
+gtk_search_entry_is_keynav (guint keyval,
+ GdkModifierType state)
{
- GdkModifierType state = 0;
- guint keyval;
-
- if (!gdk_event_get_keyval (event, &keyval))
- return FALSE;
-
- gdk_event_get_state (event, &state);
-
if (keyval == GDK_KEY_Tab || keyval == GDK_KEY_KP_Tab ||
keyval == GDK_KEY_Up || keyval == GDK_KEY_KP_Up ||
keyval == GDK_KEY_Down || keyval == GDK_KEY_KP_Down ||
{
GtkSearchEntryPrivate *priv = GET_PRIV (entry);
gboolean handled;
- guint keyval;
+ guint keyval, state;
if (!gtk_widget_get_realized (GTK_WIDGET (entry)))
gtk_widget_realize (GTK_WIDGET (entry));
gdk_event_get_keyval (event, &keyval);
+ gdk_event_get_state (event, &state);
- if (gtk_search_entry_is_keynav_event (event) ||
+ if (gtk_search_entry_is_keynav (keyval, state) ||
keyval == GDK_KEY_space ||
keyval == GDK_KEY_Menu)
return GDK_EVENT_PROPAGATE;
return handled && priv->content_changed && !priv->search_stopped ? GDK_EVENT_STOP : GDK_EVENT_PROPAGATE;
}
+
+static gboolean
+capture_widget_key_handled (GtkEventControllerKey *controller,
+ guint keyval,
+ guint keycode,
+ GdkModifierType state,
+ GtkWidget *entry)
+{
+ GtkSearchEntryPrivate *priv = gtk_search_entry_get_instance_private (GTK_SEARCH_ENTRY (entry));
+ gboolean handled;
+
+ if (gtk_search_entry_is_keynav (keyval, state) ||
+ keyval == GDK_KEY_space ||
+ keyval == GDK_KEY_Menu)
+ return FALSE;
+
+ priv->content_changed = FALSE;
+ priv->search_stopped = FALSE;
+
+ handled = gtk_event_controller_key_forward (controller, entry);
+
+ return handled && priv->content_changed && !priv->search_stopped ? GDK_EVENT_STOP : GDK_EVENT_PROPAGATE;
+}
+
+/**
+ * gtk_search_entry_set_key_capture_widget:
+ * @bar: a #GtkSearchEntry
+ * @widget: (nullable) (transfer none): a #GtkWidget
+ *
+ * Sets @widget as the widget that @entry will capture key events from.
+ *
+ * Key events are consumed by the search entry to start or
+ * continue a search.
+ *
+ * If the entry is part of a #GtkSearchBar, it is preferable
+ * to call gtk_search_bar_set_key_capture_widget() instead, which
+ * will reveal the entry in addition to triggering the search entry.
+ *
+ * Since: 3.94
+ **/
+void
+gtk_search_entry_set_key_capture_widget (GtkSearchEntry *entry,
+ GtkWidget *widget)
+{
+ GtkSearchEntryPrivate *priv = gtk_search_entry_get_instance_private (entry);
+
+ g_return_if_fail (GTK_IS_SEARCH_ENTRY (entry));
+ g_return_if_fail (!widget || GTK_IS_WIDGET (widget));
+
+ if (priv->capture_widget)
+ {
+ g_object_unref (priv->capture_widget_controller);
+ g_object_remove_weak_pointer (G_OBJECT (priv->capture_widget),
+ (gpointer *) &priv->capture_widget);
+ }
+
+ priv->capture_widget = widget;
+
+ if (widget)
+ {
+ g_object_add_weak_pointer (G_OBJECT (priv->capture_widget),
+ (gpointer *) &priv->capture_widget);
+
+ priv->capture_widget_controller = gtk_event_controller_key_new (widget);
+ gtk_event_controller_set_propagation_phase (priv->capture_widget_controller,
+ GTK_PHASE_CAPTURE);
+ g_signal_connect (priv->capture_widget_controller, "key-pressed",
+ G_CALLBACK (capture_widget_key_handled), entry);
+ g_signal_connect (priv->capture_widget_controller, "key-released",
+ G_CALLBACK (capture_widget_key_handled), entry);
+ }
+}
+
+/**
+ * gtk_search_entry_get_key_capture_widget:
+ * @entry: a #GtkSearchEntry
+ *
+ * Gets the widget that @entry is capturing key events from.
+ *
+ * Returns: The key capture widget.
+ *
+ * Since: 3.94
+ **/
+GtkWidget *
+gtk_search_entry_get_key_capture_widget (GtkSearchEntry *entry)
+{
+ GtkSearchEntryPrivate *priv = gtk_search_entry_get_instance_private (entry);
+
+ g_return_val_if_fail (GTK_IS_SEARCH_ENTRY (entry), NULL);
+
+ return priv->capture_widget;
+}
+